package com.googlecode.dgwt.client.dijit.theme.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.StyleElement;
import com.googlecode.dgwt.client.dijit.Dijit;
import com.googlecode.dgwt.client.dijit.theme.CSS;
import com.googlecode.dgwt.client.dojo.Dojo;
import com.googlecode.dgwt.client.dojo.callback.TransformCallback;
import com.googlecode.dgwt.client.dojo.core.JsFunction;
import com.googlecode.dgwt.client.dojo.string.RegExp;
import com.googlecode.dgwt.client.dojo.util.StringMap;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class StyleSheetInjectionEngine implements TransformCallback<String, String>{

    
    private static final List<CSS> userCSSRules = new ArrayList<CSS>();
    private final StringMap<CSS> themeCSSRules = new StringMap<CSS>();
    
    private static StyleElement userStyleSheetNode;
    private StyleElement themeStyleSheetNode;
    
    private static RegExp CSS_FORMAT_REGEXP = RegExp.create("\\{|\\}", "g");
    private static JsFunction CSS_FORMATTER = JsFunction.create("a", "return (a == '}' ? '\\n' : '') + a + '\\n';");

    private static String sniffRTL = null;
    private boolean sniffRTLAttached = false;
    private boolean attached = false;
    private boolean currentLTR = true;
    private Boolean themeLTR = null;
    

    /**
     * Gets the StyleSheetInjectionEngine for the current Theme
     * @return StyleSheetInjectionEngine
     */
    public static StyleSheetInjectionEngine get(){
        return Dijit.getTheme().getStyleSheetEngine();
    }
    

    /**
     * Creates a new CSS user declaration.
     * @return CSS
     */
    protected CSS create(){
        CSS css = new CSSImpl("");
        userCSSRules.add(css);
        return css;
    }

    /**
     * Creates a new CSS declaration if it doesn't already exists.
     * 
     * @param widgetBaseClass
     *            name of base-class used in this declaration
     * @return CSS
     */
    public CSS create(String widgetBaseClass){

        assert (widgetBaseClass != null 
                && widgetBaseClass.length() != 0) : "Widget Base-Class can't be empty";
        
        CSS css = themeCSSRules.get(widgetBaseClass);
        
        if (css == null){
            css = new CSSImpl(widgetBaseClass);
            themeCSSRules.put(widgetBaseClass, css);
        }
        
        return css;
    }
    
    
    /**
     * Performs parameterized substitutions on a CSS.
     */
    public String transform(String key, String value) {
        if ("moduleBaseURL".equals(key)){
            return GWT.getModuleBaseURL();
        }
        if ("hostPageBaseURL".equals(key)){
            return GWT.getHostPageBaseURL();
        }
        if ("moduleName".equals(key)){
            return GWT.getModuleName();
        }
        
        value = ((StyleSheetTheme)Dijit.getTheme()).getProperty(key);
        if (value != null){
            return value;
        }
        
        System.err.println("value not found for CSS key: " + key);
        return "";
    }
    
    
    /**
     * Update the current direction
     */
    public void updateDirection() {
        setLtr(themeLTR);        
    }

    
    /**
     * Sets the Theme default direction for elements
     * 
     * @param ltr True if is Left-To-Right direction
     */
    protected void setLtr(Boolean ltr) {
        
        this.themeLTR = ltr;
        boolean newLTR = ltr == null ? Dojo.isBodyLTR() : ltr;
        
        if (this.currentLTR != newLTR) {
            this.currentLTR = newLTR;
            
            if (attached) {
                detach();
                attach();
            }
        }
    }


    /**
     * Gets the current default direction for elements
     * 
     * @return True if is Left-To-Right direction
     */
    protected boolean isLtr() {
        return currentLTR;
    }


    /**
     * Publish all cached CSS declarations to the document.
     */
    protected void attach() {
        attached = true;
        sniffRTLAttached = !isLtr() && sniffRTL != null;
        
        if (sniffRTLAttached) {
            Dojo.addClass(Document.get().getDocumentElement(), sniffRTL);
        }
        
        for (CSS css : themeCSSRules.values()) {
            css.importDeclaration();
        }
    }

    
    /**
     * Remove all CSS Theme declarations published in document.
     */
    protected void detach() {
        attached = false;
        
        if (themeStyleSheetNode != null){
            themeStyleSheetNode.getParentElement().removeChild(themeStyleSheetNode);
            themeStyleSheetNode = null;

            for (CSS css : themeCSSRules.values()) {
                ((CSSImpl)css).markAsUnpublished();
            }
        }
        
        if (sniffRTLAttached) {
            sniffRTLAttached = false;
            Dojo.removeClass(Document.get().getDocumentElement(), sniffRTL);
        }
    }

    
    protected void userPublish(String css) {
        if (css != null && !"".equals(css)){
            userStyleSheetNode = internalPublish(css, userStyleSheetNode, null);
        }
    }
    
    
    protected void themePublish(String css) {
        if (css != null && !"".equals(css)){
            themeStyleSheetNode = internalPublish(css, themeStyleSheetNode, userStyleSheetNode);
        }
    }
    
    
    protected StyleElement internalPublish(String css, StyleElement cssNode, StyleElement nextNode) {
        css = Dojo.string.substitute(css, this);
        css = CSS_FORMAT_REGEXP.replace(css, CSS_FORMATTER);
        
        if (cssNode == null){
            cssNode = Document.get().createStyleElement();
            cssNode.setAttribute("type", "text/css");
            
            insertCSSNode(cssNode, nextNode);
            addCSSText(cssNode, css, true);
        } else {
            addCSSText(cssNode, css, false);
        }
        
        return cssNode;
    }
    
    
    protected void insertCSSNode(Element cssElement, StyleElement nextNode){
        Element parent;
        NodeList<Element> nodeList = Document.get().getElementsByTagName("head");
        if (nodeList.getLength() > 0){
            parent = nodeList.getItem(0);
            
            if (nextNode == null) {
                //nextNode = GWT_ScriptElement;
            }            
        } else {
            parent = Document.get().getBody();
        }

        parent.insertBefore(cssElement, nextNode);
    }
    
    
    protected native void addCSSText(Element st, String css, boolean firstInsertion) /*-{
        if (st.styleSheet) {
            if (firstInsertion){
                st.styleSheet.cssText = css;
            } else {
                st.styleSheet.cssText += css;
            }
        } else {
            st.appendChild($doc.createTextNode(css));
        }
    }-*/;
    

    public void sniff(){
        if (sniffRTL != null || !Dijit.isLoaded()) {
            return;
        }
        
        Map<String, Float> nav = Dojo.navigator.toMap();

        String styleClass = "";
        String styleClassRtl = "";
        
        for (String name : nav.keySet()){
            float value = nav.get(name);
                
            if (value > 0){
                name = name.toLowerCase().replaceAll("^is", "dj_");
                
                int iValue = (int) Math.floor(value);
                if (iValue > 1){
                    name += " " + name + iValue;
                }
                styleClass += " " + name;
                styleClassRtl += " " + name + "-rtl";
            }
        }
        
        String boxModel = "dj_" + Dojo.getBoxModel().toString().replace("-", "");
        styleClass += " " + boxModel;
        styleClassRtl += " " + boxModel + "-rtl" + " dijitRtl";         
        
        Dojo.addClass(Document.get().getDocumentElement(), styleClass);
        sniffRTL = styleClassRtl;
        updateDirection();
    }

    
    /**
     * Represents a fragment of Cascade Style Sheet 
     * declaration for a specific Dijit Widget
     */
    public class CSSImpl extends CSS {

        /**
         * Creates a new CSS declaration
         * 
         * @param widgetBaseClass
         *            name of base-class used in this declaration
         */
        private CSSImpl(String widgetBaseClass){
            super(widgetBaseClass);
        }

        
        /**
         * Publish the declaration into the HTML document 
         * @see com.googlecode.dgwt.client.dijit.theme.CSS#publish(java.lang.String)
         */
        @Override
        protected boolean publish(String declarationLTR, String declarationRTL){            
            if (StyleSheetInjectionEngine.get() == StyleSheetInjectionEngine.this) {
                String declaration = isLtr() ? declarationLTR : declarationRTL;
                
                if (isAnonymous()) {
                    userPublish(declaration);
                } else {
                    themePublish(declaration);
                }
                
                return true;
            }
            return false;
        }

        protected void markAsUnpublished() {
            setPublished(false);
        }

    }

}
